const Cc = Components.classes;
const Ci = Components.interfaces;

const kPWDMGR_CID = "@mozilla.org/passwordmanager;1";
const kHTTP_POLL = "@mozilla.org/httppolling;1";
const kHTTP_BIND = "@mozilla.org/httpbinding;1";


const nsIHttpPolling = Ci.nsIHttpPolling;
const nsIHttpBinding = Ci.nsIHttpBinding;

const kXMPP_CID = "@mozilla.org/xmpp;1";
const kXMPP_UID = Components.ID("8abf9999-7546-4add-83a3-7dbd30df5540");

const nsIXmppService = Ci.nsIXmppService;
const nsISupports = Ci.nsISupports;

const kXMLDOC_CID = "@mozilla.org/xml/xml-document;1";
const nsIXMLDocument = Ci.nsIDOMDocument;
const XMLDocument = Ci.nsIDOMXMLDocument;

const kXMLSerializer = "@mozilla.org/xmlextras/xmlserializer;1";
const nsIDOMSerializer = Ci.nsIDOMSerializer;

const kWINDOW = "@mozilla.org/embedcomp/window-watcher;1";
const nsIWindowWatcher = Ci.nsIWindowWatcher;

const kRDF = "@mozilla.org/rdf/rdf-service;1";
const kRDFCU = "@mozilla.org/rdf/container-utils;1"

const dom = Cc[kXMLDOC_CID].getService(nsIXMLDocument);
const kRDFPFX = "http://xmpp.org/RDF#";
const kTIMER_CID = "@mozilla.org/timer;1";

const kHOST = "jazzila.server.host";
const kPORT = "jazzila.server.port";
const kRESOURCE = "jazzila.server.resource";
const kRMBPWD = "jazzila.remember.password";

const _DEBUG = true;

const _DISCONNECTED = 0;
const _CONNECTED = 1;
const _AUTHENTICATED = 3;
const fireTimer = Cc[kTIMER_CID].getService(Ci.nsITimer);

const xmlserialiser = Cc[kXMLSerializer].getService(nsIDOMSerializer);
const parser = Cc["@mozilla.org/xmlextras/domparser;1"].getService(Ci.nsIDOMParser);

function nsXmppService ()
{
  this.prefBranch = null;
  this.listeners = new Array();
  this.connection = null;
  this.state = _DISCONNECTED;
  this.use_digest = false;
  this.domain = null;
  this.user = null;
  this.jid = null;
  this.pass = null;
  this.http_gw =  null;
  this.http_user = null;
  this.http_pass = null;
  this.http_bind = false;
  this.rdf = null;
  this.data=null;
  this.cu = null;
  this.register = false;
  this.schedule = new Array();
  this.queue = new Array();
  this.presence = {
	show : null,
	status : null,
	priority: 0
  };
  var parent = this;
  this.PrefChangeObserver = {
    observe: function(aSubject, aTopic, aData)
    {
      parent.prefChanged(aData);
    }
  };

  this.addObserver("jazzila", this.PrefChangeObserver);
  this.getRosterDS = function (){ return this.data; };
  this.getMessagesDS = function (){ return this.data; };
  this.getDataSource = function (){ return this.data; };
  this.initDataSources();
}

nsXmppService.prototype.streamID = null;
nsXmppService.prototype.sasl_mechanisms = new Array();
nsXmppService.prototype.setHttpUser = function(user){ this.user=user; }
nsXmppService.prototype.setHttpPassword = function(pass){ this.http_pass=pass; }
nsXmppService.prototype.setUser = function(user){this.user=user;}
nsXmppService.prototype.setPassword=function(pass){this.pass=pass; }
nsXmppService.prototype.setRegisterFirst = function(register){this.register=register;}
nsXmppService.prototype.addListener = function(aListener) 
{
  this.listeners.push(aListener);
  aListener.onStateChange(this.state);
  return this.listeners.length;
}

nsXmppService.prototype.removeListener = function(aListener) {
  var found = false;
  var run = 0;
  
  while (!found || (run < this.listeners.length)) {
    if (this.listeners[run] == aListener) {
      this.listeners[run] = null;
      found = true;
    }
    run++;  
  }
}

nsXmppService.prototype.setState = function(state){
   if(this.state == state)
      return;
   this.state = state;
   for(var i=0; i < this.listeners.length; i++){
	   if (this.listeners[i])
	       this.listeners[i].onStateChange(this.state);
   }
}

nsXmppService.prototype.openRoster = function()
{
	var browser = Cc[kWINDOW].getService(nsIWindowWatcher);
	var win = browser.openWindow(null, "chrome://jazzila/content/roster.xul", "Roster", null, null);
	win.document.title = "xmpp";
}

nsXmppService.prototype.reg1 = function()
{
  var query = this.createIQ("reg1", "get", "jabber:iq:register", "query");
  this.send(query.ownerDocument);
}

nsXmppService.prototype.unregister = function ()
{
  var query = this.createIQ("reg1", "get", "jabber:iq:register", "query");
  var doc = query.ownerDocument;
  query.appendChild(doc.createElement("remove"));
  this.send(doc);
}
nsXmppService.prototype.closeRegisterForm = function (fields)
{
	var browser = Cc[kWINDOW].getService(nsIWindowWatcher);
	var win = browser.getWindowByName("registerForm", null);
	if (win)
		win.close();

}

nsXmppService.prototype.openRegisterForm = function (fields)
{
	var browser = Cc[kWINDOW].getService(nsIWindowWatcher);
	var win = browser.getWindowByName("registerForm", null);
	if (!win)
	{
		win = browser.openWindow(null, "chrome://jazzila/content/register.xul", "registerForm", "chrome,centerscreen", fields);
		
	}
	else if (!fields.childNodes)
	{
		win.close();
		return win;
	}

	return win;
}

nsXmppService.prototype.onReg1 = function(q)
{
	var doc = q.ownerDocument;	
	var usertag = q.getElementsByTagName("username").item(0);
	var passwordtag = q.getElementsByTagName("password").item(0);
	usertag.appendChild(doc.createTextNode(this.user));
	passwordtag.appendChild(doc.createTextNode(this.pass));
	var win = this.openRegisterForm(q.childNodes);
}

nsXmppService.prototype.onReg2= function(e)
{
	var code=e.getAttribute("code");
	var ww = Cc[kWINDOW].getService(nsIWindowWatcher);
	var msg = "Unresolved error";
	switch (code)
	{
		case "401":
			msg = "Registration not allowed";
			break;
		case "406":
			msg = "Some Required Information Not Provided";
			break;
		case "409":
			msg = "Username Conflict";
			break;
	}
	ww.getNewPrompter(win).promptPassword("xmpp", msg);
}	

nsXmppService.prototype.doReg2 = function(args)
{
  var query = this.createIQ("reg2", "set", "jabber:iq:register", "query");
  var doc = query.ownerDocument; 
  for (var i=0;i<args.length; i++)
  {
	 query.appendChild(doc.createElement(args[i].nodeName)).appendChild(doc.createTextNode(args[i].firstChild.nodeValue));
  }
 this.send(doc);
}

nsXmppService.prototype.registerIn = function(nodes)
{
   this.doReg2(nodes);
   if(!this.register)
   {
		this.setJid(jid);
		this.setPassword(password);
		this.auth1();
   }
   return !this.register;
}

nsXmppService.prototype.doAuthenticated=function()
{
    this.setState(_AUTHENTICATED);
    this.schedule["info1"] ="onInfo";
    this.discovery("info1", "info");
    this.sendPresence();
}

nsXmppService.prototype.onAuthError = function(node)
{
    var ww = Cc[kWINDOW].getService(nsIWindowWatcher);
    var code = node.getAttribute("code");
    switch (code)
    {
	case "401":
	    var pass={value:""};
		var check={checked:this.getPrefBranch().getBoolPref(kRMBPWD)};
		server=this.getPrefBranch().getCharPref(kHOST);
		if(!ww.getNewPrompter(null).promptPassword("xmpp", "Bad password " + this.user, pass, "remember password", check)) {
			this.disconnect();
			return;
		}
		this.pass = pass.value;
		if (check.checked)
			this.savePasswd(server, this.user, this.pass);
		this.doAuth(q);
	break;
	case "406":
	    debug("Not Acceptable");

	break;
	case "409":
	    break;
    }

}

nsXmppService.prototype.auth1 = function()
{
    var query = this.createIQ("auth1", "get", "jabber:iq:auth", "query");
    var doc = query.ownerDocument;
    var user = doc.createElement('username');
    user.appendChild(doc.createTextNode(this.user.toString()));
    query.appendChild(user);
    this.send(doc);
}


nsXmppService.prototype.auth2 = function(q)
{
    var sha1 = Cc["@mozilla.org/cryptoservice;1"].getService(Ci.nsICryptoService);
    var digest = q.getElementsByTagName("digest").length;
    var sequence = q.getElementsByTagName("sequence");
	var token = q.getElementsByTagName("token");
    var xml = this.connection.responseXml;
    var query = this.createIQ("auth2", "set", "jabber:iq:auth", "query");
	var doc = query.ownerDocument;
    var userNode = doc.createElement('username');
    var passNode = doc.createElement('password');
    var rsrcNode = doc.createElement('resource');
    userNode.appendChild(doc.createTextNode(this.user.toString()));
    passNode.appendChild(doc.createTextNode(this.pass.toString()));
    rsrcNode.appendChild(doc.createTextNode(this.resource));
    query.appendChild(userNode);
	if (token.length)
	{	
		var strtok = token.item(0).firstChild.nodeValue;
		var N = parseInt(sequence.item(0).firstChild.nodeValue); 
		var hash=sha1.hex_sha1(sha1.hex_sha1(this.pass)+strtok);
		debug ("Token: " +strtok);
		debug ("Sequence: " + N);
		for (var i=0; i<N; i++)
		{ 
			hash = sha1.hex_sha1(hash);
		}
		query.appendChild(doc.createElement('hash')).appendChild(doc.createTextNode(hash));
	}
	else if (digest)
	{ 
		var hash= sha1.hex_sha1(this.streamID+ this.pass);	
		query.appendChild(doc.createElement('digest')).appendChild(doc.createTextNode(hash));
    }
    else 
		query.appendChild(passNode);
    query.appendChild(rsrcNode);
    this.send(doc);
}

nsXmppService.prototype.session = function ()
{
  var doc = this.createIQ("sess1", "set", "urn:ietf:params:xml:ns:xmpp-session", "session").ownerDocument;
  this.send(doc);
  this.schedule["sess1"] = "doAuthenticated";
  this.wait("sess1", 5000, "doAuthenticated");
}

nsXmppService.prototype.onBind = function (bind)
{
    var jid = bind.firstChild;
    if (jid)
    {
        debug (bind.firstChild.nodeName +": " + jid.firstChild.nodeValue);
	if(jid.firstChild.nodeValue)
	    	this.jid = jid.firstChild.nodeValue;
    }
}

nsXmppService.prototype.bind = function ()
{
  var q = this.createIQ("bind1","set", "urn:ietf:params:xml:ns:xmpp-bind", "bind");
  var doc = q.ownerDocument;
  var elm = doc.createElement("resource");
  elm.appendChild(doc.createTextNode(this.resource));
  q.appendChild(elm);
  this.schedule["bind1"] = "onBind";
  this.send(doc);
}

nsXmppService.prototype.setPresence = function (show, status)
{
    if(!show)
	show = "online";
    this.presence.show = show;
    if(status)
	this.presence.status = status;
    else 
	try
	{
	    this.presence.status = this.getPrefBranch().getCharPref("jazzila.message." + show);
	}
    catch (e){}
    this.sendPresence();
}

nsXmppService.prototype.sendPresence = function ()
{
    //  var doc = dom.implementation.createDocument("","", null);
    var doc = Cc["@mozilla.org/xml/xml-document;1"].createInstance();
    //  var presence = doc.createElementNS("jabber:client", "presence"); 
    var presence = doc.createElement("presence"); 
    var show = this.presence.show;
    switch (show)
    {
	case "offline":
	    presence.setAttribute("type", "unavailable");
	break;
	case "online":
	    break;
	default:
	if (show)
	    presence.appendChild(doc.createElement('show')).appendChild(doc.createTextNode(show));
	break;
    }
    presence.appendChild(doc.createElement("priority")).appendChild(doc.createTextNode(this.presence.priority));
    if (this.presence.status)
	presence.appendChild(doc.createElement("status")).appendChild(doc.createTextNode(this.presence.status));
    doc.appendChild(presence);
    this.send(doc);
}

nsXmppService.prototype.getRoomList = function(jid)
{
    this.schedule["rooms"] = "fillRoomlist"
    this.discovery("rooms", "items", jid);
}

nsXmppService.prototype.createRoom = function (room, nick)
{
	room = this.rdf.GetResource("urn:xmpp:muc:room:" + room);
	this.Assert(room, this.rdf.GetResource("urn:xmpp:muc:room#nick"), this.rdf.GetLiteral(nick));
}

nsXmppService.prototype.leave = function (room, reason)
{
    var doc = dom.implementation.createDocument("","", null);
    var presence = doc.createElement("presence");
    var nick = this.GetLiteral("urn:xmpp:muc:room:" + room, "urn:xmpp:muc:room#nick");
    presence.appendChild(doc.createElement("x")).setAttribute("xmlns", "http://jabber.org/protocol/muc");
    presence.setAttribute("from", this.jid);
    presence.setAttribute("to", room+"/" + nick);
    presence.setAttribute("type", "unavailable")
    if (reason)
    {
	    var status  = doc.createElement("status");
	    status.appendChild(doc.createTextNode(reason));
	    presence.appendChild(status);
    }
    doc.appendChild(presence);
    this.send(doc);
}

nsXmppService.prototype.join = function (room, nick)
{
    var doc = dom.implementation.createDocument("","", null);
    var presence = doc.createElement("presence");
    presence.appendChild(doc.createElement("x")).setAttribute("xmlns", "http://jabber.org/protocol/muc");
    presence.setAttribute("from", this.jid);
    presence.setAttribute("to", room+"/" + nick);
    doc.appendChild(presence);
    this.createRoom(room, nick);
    this.send(doc);
    this.discovery("info1", "info", room);
}

nsXmppService.prototype.doRoster = function()
{
    var doc = this.createIQ("roster1", "get", "jabber:iq:roster", "query").ownerDocument;
    var iq = doc.firstChild;
    iq.setAttribute("to", this.domain);
    iq.setAttribute("from", this.jid);
    this.send(doc);
}

nsXmppService.prototype.reqSubscr = function(jid, type)
{
    var doc = dom.implementation.createDocument("","", null);
    var presence = doc.createElement("presence");
    presence.setAttribute("type", (type ? "subscribe": "unsubscribe"));
    presence.setAttribute("to", jid);
    doc.appendChild(presence);
    this.send(doc);
}

nsXmppService.prototype.unsubscribe = function(jid)
{
    this.reqSubscr(jid, false);
}

nsXmppService.prototype.subscribe = function(jid)
{
    this.reqSubscr(jid, true);
}

nsXmppService.prototype.allowSubscr = function(jid, type)
{
    var doc = dom.implementation.createDocument("","", null);
    var presence = doc.createElement("presence");
    presence.setAttribute("type", (type ? "subscribed": "unsubscribed"));
    presence.setAttribute("to", jid);
    doc.appendChild(presence);
    this.send(doc);
}

nsXmppService.prototype.allowSubscribtion = function(jid)
{
	this.allowSubscr(jid, true);
}

nsXmppService.prototype.denySubscribtion = function(jid)
{
	this.allowSubscr(jid, false);
}

nsXmppService.prototype.onSubscribe = function(jid, type)
{
	var ww = Cc[kWINDOW].getService(nsIWindowWatcher);
	var prompt=ww.getNewPrompter(null);
    switch (type)
    {
	case "subscribe":
		var rsp=["subscribed", "unsubscribed"];
	    var out = {value: false};
		var flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_IS_STRING +
            prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_IS_STRING  +
            prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING;
		var code = prompt.confirmEx("Add to contact list ?", jid + " asks to put you on his contact list", flags, "accept", "deny", "later", null, out);
		debug("Code: " + code);
		if (code < 2)
		{
			this.connection.send('<presence from="'+jid+' type="'+rsp[code]+'"/>');
		}
	break;
	case "unsubscribe":
	    if (prompt.confirmEx("Remove from contact list" , jid + " asks to be removed from your contact list", 127, "accept", "deny", "later", null))
		{
		}
	break;
    }
}

nsXmppService.prototype.removeRosterItem = function (jid)
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 	
}

nsXmppService.prototype.doState = function()
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 
    iq.setAttribute("type","get");
    iq.setAttribute("id", "jwchat_state");
    var query =doc.createElement("query");
    query.setAttribute("xmlns", "jabber:iq:private");
    var mychat = doc.createElement("jwchat"); 
    mychat.setAttribute("xmlns", "jwchat:state");
    query.appendChild(mychat);
    iq.appendChild(query);
    doc.appendChild(iq);
    this.send(doc);
}

nsXmppService.prototype.doPrefs = function()
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 
    iq.setAttribute("type","get");
    iq.setAttribute("id", "jwchat_prefs");
    var query =doc.createElement("query");
    query.setAttribute("xmlns", "jabber:iq:private");
    var mychat = doc.createElement("jwchat"); 
    mychat.setAttribute("xmlns", "jwchat:prefs");
    query.appendChild(mychat);
    iq.appendChild(query);
    doc.appendChild(iq);
    this.send(doc);
}

nsXmppService.prototype.createIQ = function(id, type, ns, tag)
{
    //  var doc = dom.implementation.createDocument("","", null);
    var doc = Cc["@mozilla.org/xml/xml-document;1"].createInstance();
    var iq = doc.createElementNS("jabber:client", "iq"); 
    iq.setAttribute("to", this.domain);
    iq.setAttribute("type", type);
    iq.setAttribute("id", id);
    doc.appendChild(iq);
	var query = doc.createElementNS(ns, tag);
	iq.appendChild(query);
    return query;
}


nsXmppService.prototype.onItems= function (q)
{
    var items = q.getElementsByTagName("item");
    for( var i = 0; i< items.length; i++)
    {
	var item = items.item(i);
	var jid = item.getAttribute("jid");
	var node = item.getAttribute("node");
	this.schedule["info_" + i] =  "onInfo"
	this.discovery( "info_" + i, 'info', jid, node);
    }
}

nsXmppService.prototype.doBrowse= function ()
{
    var query = this.createIQ("browse1", "get", "jabber:iq:browse", "query");
    this.send(query.ownerDocument);	
}

nsXmppService.prototype.onInfo = function (q)
{
    var ds = this.getDataSource();
    var features = q.getElementsByTagName("feature");
    var ids = q.getElementsByTagName("identity");
    var iq = q.parentNode;
    var jid = iq.getAttribute("from");
    var from = this.rdf.GetResource("urn:xmpp:identity:" + jid);
    for( var i = 0; i< ids.length; i++)
    {
	var svc = ids.item(i);
	var cat = svc.getAttribute("category");
	var type = svc.getAttribute("type");
	var name = this.rdf.GetLiteral(svc.getAttribute("name"));;
	var ctn  = this.cu.MakeSeq(ds, this.rdf.GetResource("urn:xmpp:identity:category:"+cat+":"+type));
	if(ctn.IndexOf(from)<0)
		ctn.AppendElement(from);
	this.Assert(from, this.rdf.GetResource("urn:xmpp:identity#name"), name);
	this.Assert(from, this.rdf.GetResource("urn:xmpp:identity#jid"), this.rdf.GetLiteral(jid));
    }

    ctn = this.cu.MakeSeq(ds, from);
    for( var i = 0; i< features.length; i++)
    {
	var svc = features.item(i);
	var ftr = svc.getAttribute("var");
	if (!ftr)	ftr = svc.getAttribute("type");
	if (!ftr)	return;
	var proto = this.rdf.GetResource(ftr);
	if(ctn.IndexOf(proto)<0);
		ctn.AppendElement(proto);
    }
    
    if (ctn.IndexOf(this.rdf.GetResource("http://jabber.org/protocol/disco#items"))>-1) 
    {
        this.schedule["items"] = "onItems";
        this.discovery("items", "items", iq.getAttribute("from"), q.getAttribute("node"));
    }
}

nsXmppService.prototype.getVCard = function(jid)
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 
    var v = "1";
    iq.setAttribute("type","get");
    if (jid != this.user){
	v = "3";
	iq.setAttribute("to", jid);
    }
    iq.setAttribute("id", "v" + v);
    iq.appendChild(doc.createElement("vCard")).setAttribute("xmlns", "vcard-temp");
    doc.appendChild(iq);
    this.send(doc);
}

nsXmppService.prototype.DOM2RDF = function (prefix, node)
{
    var rdf = Cc[kRDF].getService(Ci.nsIRDFService);
    var ds = this.getDataSource();
    var url = rdf.GetResource(prefix);
    var nodes = node.childNodes;
    var name = node.nodeName.toLowerCase()
	for(var i=0; i<nodes.length; i++) 
	{
	    var child = nodes.item(i);
	    switch (child.nodeType)
	    {
		case node.TEXT_NODE:
		    var value = rdf.GetLiteral(child.nodeValue);
		    var predicate = rdf.GetResource(kRDFPFX + name);
		    this.Assert(url, predicate, value, true);
		    debug(prefix + ":" + name + ": " + child.nodeValue);
		    break;
		case node.ELEMENT_NODE:
		    var predicate = rdf.GetResource(prefix + ":"+ name);
		    var value = rdf.GetResource(prefix+":"+name);
		    this.Assert(url, predicate, value, true);
		    this.DOM2RDF(prefix+":"+name,  child);
		    break;
	    }
	}
}

nsXmppService.prototype.onVCard = function(jid, vcard)
{
    var nodes = vcard.childNodes;
    for(var i=0; i<nodes.length; i++)
	this.DOM2RDF("urn:xmpp:vcard-temp:"+jid, nodes.item(i));
    this.openVCardWindow(jid);
}


nsXmppService.prototype.openVCardWindow = function(jid)
{
    var browser = Cc[kWINDOW].getService(nsIWindowWatcher);
    var name = "urn:xmpp:vcard-temp:" + jid;
    var win = browser.getWindowByName(name, null);
    if (!win)
    {
	var attrs = Array(jid);
	win = browser.openWindow(null, "chrome://jazzila/content/vcard.xul", name, "chrome,resizable=1", attrs);
	win.name = "urn:xmpp:vcard-temp:" + jid;
	win.document.title = "vCard:" + jid;
	var doc = win.document.documentElement;
	doc.setAttribute ("jid", jid);
    }
    win.focus();
}

nsXmppService.prototype.openSearchWindow = function(q)
{
    var browser = Cc[kWINDOW].getService(nsIWindowWatcher);
    var name = "urn:xmpp:search";
    var win = browser.getWindowByName(name, null);
    if (!win)
    {
	win = browser.openWindow(null, "chrome://jazzila/content/vcard.xul", name, "chrome,resizable=1", null);
	win.document.title = "Search contacts";
    }
    win.focus();

}

nsXmppService.prototype.discovery = function(qid, type, jid, node)
{
    var q = this.createIQ(qid, "get", "http://jabber.org/protocol/disco#"+type,"query");
    var iq = q.parentNode;
    if(jid)
	iq.setAttribute("to", jid);
    if (node)
    	q.setAttribute("node", node);
    this.send(q.ownerDocument);
}

nsXmppService.prototype.search = function ()
{
    var query = this.createIQ("search1", "get", "jabber:iq:search", "query");
	this.send(query.ownerDocument);
}

nsXmppService.prototype.doBrowse= function ()
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 
    iq.setAttribute("type","get");
    iq.setAttribute("from", this.user + "@"+this.domain);
    iq.setAttribute("to", this.domain+ "/");
    iq.setAttribute("id", "browse1");
    iq.appendChild(doc.createElementNS("jabber:iq:browse", "query"));
    doc.appendChild(iq);
    this.send(doc);
}

nsXmppService.prototype.doSearch1 = function (directory)
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 
    iq.setAttribute("type","get");
    iq.setAttribute("from", this.user + "/"+this.resource);
    iq.setAttribute("to", directory);
    iq.setAttribute("id", "search1");
    iq.appendChild(doc.createElement("query")).setAttribute("xmlns", "jabber:iq:search");
    doc.appendChild(iq);
    this.send(doc);
}

nsXmppService.prototype.onSearch1 = function (id, query)
{	
    var nodes = query.childNodes;
    for (var i=0; i<nodes.length; i++)
    {
	item=nodes.item(i);
	switch (item.nodeName)
	{
	    case "x":
		this.onExtendedSearch(item);
	    break;
	    default:
	    this.onSearchItem(item);
	    break;
	}
    }
}

nsXmppService.prototype.onExtendedSearch = function (id, x)
{
    var type = x.getAttribute("type");
    var ns = x.getAttribute("xmlns");
}

nsXmppService.prototype.onSearch = function (id, query)
{	
    if (id=="search1")
    {
	this.onSearch1(query);
    }
    else
    {
	this.onSearch2(query);
    }
}

nsXmppService.prototype.doSearch2 = function(directory, first, last, nick, email)
{
    var doc = dom.implementation.createDocument("","", null);
    var iq = doc.createElement("iq"); 
    var query = doc.createElement("query");
    iq.setAttribute("type","get");
    iq.setAttribute("from", this.user + "/"+this.resource);
    iq.setAttribute("to", directory);
    iq.setAttribute("id", "search1");
    if (first)
	query.appendChild(doc.createElement("first")).appendChild(doc.createTextNode(first));
    if (last)
	query.appendChild(doc.createElement("last")).appendChild(doc.createTextNode(last));
    if (nick)
	query.appendChild(doc.createElement("nick")).appendChild(doc.createTextNode(nick));
    if (email)
	query.appendChild(doc.createElement("email")).appendChild(doc.createTextNode(email));

    iq.appendChild(query).setAttribute("xmlns", "jabber:iq:search");
    doc.appendChild(iq);
}

nsXmppService.prototype.onSearch2 = function (result)
{
    var rdf = Cc[kRDF].getService(Ci.nsIRDFService);
    var ds = this.getSearchDS();
    var items = result.childNodes;
    var ctn = cu.MakeSeq(ds, rdf.GetResource("urn:xmpp:search:results"));
    for (var i = 0; i < nodes.length; i++)
    {
	var item = items.item(i);
	var jid = node.getAttribute("jid");		
	var rst = rdf.GetResource("urn:xmpp:search:result:" + ctn.GetCount()+1);
	var nodes = item.childNodes;
	for (var j=0; j < childs.length ; j++)
	{
	    var node = nodes.item(j);	
	    var value = item.firstChild.nodeValue;
	    var url = kRDFPFX + item.nodeName;
	    this.Assert(rst, rdf.GetResource(url), value, true);
	}
	this.Assert(rst, rdf.GetResource(kRDFPFX+jid), rdf.GetLiteral(jid), true);
    }
}

nsXmppService.prototype.onQuery = function (id, q, e)
{
    switch (q.getAttribute("xmlns"))
    {
	case "jabber:iq:register":
	    this.onReg(id, q, e);
	break;
	case "jabber:iq:auth":
	    this.onAuth(id, q, e);
	break;
	case "jwchat_state":
	    this.onSavedState(q);
	break;
	case "jabber:iq:roster":
	    this.onRoster1(q);
	break;
	case "http://jabber.org/protocol/disco#items":
	    this.onDiscoItems(q);
	break;
	case "http://jabber.org/protocol/disco#info":
	    this.onDiscoInfo(q);
	break;
	case "jabber:iq:search":
	    this.onSearch(id, q);
	break;
	case "search2":
	    this.onSearch2();
	break;
	case "v3":
	    this.onVCard(iq.getAttribute("from"), q);
	break;
    }
}

nsXmppService.prototype.onIQ = function(iq)
{
    var id = iq.getAttribute("id");
    var from = iq.getAttribute("from");
    var type = iq.getAttribute("type");
    var query = iq.getElementsByTagName("query");
    var error = iq.getElementsByTagName("error");
    var result = (type && type == "result" ? true: false);
    switch (id)
    {
		case "reg1":	
			this.onReg1(query.item(0));
		break;
		case "reg2":
			if(result)
			{
				this.closeRegisterForm();
				this.register=false;
				this.negotiate();;
			}
			else
			{ 
				this.onRegError(error.item(0));
			}
		break;
		case "auth1":
			this.auth2(query.item(0));
		break;
		case "auth2":
			if(result) 
				this.doAuthenticated();	
			else 
				this.onAuthError(query.item(0));
		break;
		default:
			if(result)
				this.fire(id, iq.firstChild);
		break;
    }
}

nsXmppService.prototype.assertRosterItem = function (ctn, jid, name)
{
    var rdf = Cc[kRDF].getService(Ci.nsIRDFService);
    var ds = this.getRosterDS();
    var item = rdf.GetResource("urn:xmpp:roster:item"+jid);
    if (ctn.IndexOf(item)<0)
    {
	ctn.AppendElement(item);
	ds.Assert(item, rdf.GetResource("http://xmpp.org/RDF#jid"), rdf.GetLiteral(jid), true);
	ds.Assert(item, rdf.GetResource("http://xmpp.org/RDF#name"), rdf.GetLiteral(name),true);
    }
    else 
    { 
	ds.Change(item, 
		rdf.GetResource("http://xmpp.org/RDF#jid"), 
		ds.GetTarget(item, rdf.GetResource("http://xmpp.org/RDF#jid"),true), 
		rdf.GetLiteral(jid));

	ds.Change(item, 
		rdf.GetResource("http://xmpp.org/RDF#name"),
		ds.GetTarget(item, rdf.GetResource("http://xmpp.org/RDF#name"), true), 
		rdf.GetLiteral(name));
    }
}

nsXmppService.prototype.onRoster1 = function(query)
{
    var rdf = Cc[kRDF].getService(Ci.nsIRDFService);
    var ds = this.getRosterDS();
    var rdfCU = Cc[kRDFCU].getService(Ci.nsIRDFContainerUtils); 
    var roster = rdf.GetResource("urn:xmpp:roster");
    var server = rdf.GetResource("urn:xmpp:roster:server:"+this.domain);
    var nodes = query.childNodes;
    ds.beginUpdateBatch();
    var ctnRoster = rdfCU.MakeBag(ds, roster);
    var ctnServer = rdfCU.MakeSeq(ds, server);

    if (ctnServer.IndexOf(server)<0)
    {
	ctnRoster.AppendElement(server);
    }

    for (var i = 0; i < nodes.length; i++)
    {
		var node = nodes.item(i);
		var jid = node.getAttribute("jid");
		var name = node.getAttribute("name");
		var gtag = node.getElementsByTagName("group");
		var groups = [""];
		for (var j=0; j<gtag.length;j++)
		{
			groups[j] = groups.item(j).firstChild.nodeValue;
		}
		for (j in groups )
		{
			var ctn = ctnServer;
			if (j != "")
			{			
				var group = ds.getResource("urn:xmpp:roster:group:"+j);
				var ctn = rdfCU.MakeSeq(ds);
				if (ctnServer.IndexOf(group)<0)
				{
					ctnServer.AppendElement(group);
				}
			}
			this.assertRosterItem(ctn, jid, (name==""?name:jid.substring(0, jid.indexOf('@'))));
		}
    }
    ds.endUpdateBatch();
    this.setPresence();
}

nsXmppService.prototype.Unassert =  function(item, predicate, value)
{
    this.data.Unassert(item, predicate, value, true);
}

nsXmppService.prototype.GetLiteral =  function(item, predicate)
{
	item = this.rdf.GetResource(item);
	predicate = this.rdf.GetResource(predicate);
    var target = this.data.GetTarget(item, predicate, true);
	if (target)
	{
		return target.Value;
	}
	return null;
}

nsXmppService.prototype.Assert =  function(item, predicate, value)
{
    this.data.Assert(item, predicate, value, true);
}

nsXmppService.prototype.updateMucPresence =  function(from, role, affiliation, status, type)
{
   var ds = this.getDataSource();
   var room = this.rdf.GetResource("urn:xmpp:muc:room:"+from.substring(0, from.indexOf("/")));
   var member = this.rdf.GetResource("urn:xmpp:muc:member:"+from);
   var nickname = from.substring(from.indexOf("/")+1);
   var ctnRoom = this.cu.MakeSeq(ds, room); 
   if (type == "unavailable") {
	 if(ctnRoom.IndexOf(member)<0) {
	    ctnRoom.RemoveElement(member, true);
	 }
    this.Unassert(member, this.rdf.GetResource("urn:xmpp:muc:member#nick"), this.rdf.GetLiteral(from));
	this.Unassert(member, this.rdf..GetResource("urn:xmpp:muc:member#role"), this.rdf.GetLiteral(role));
	this.Unassert(member, this.rdf.GetResource("urn:xmpp:muc:member#affiliation"), this.rdf.GetLiteral(affiliation));
	this.Unassert(member, this.rdf.GetResource("urn:xmpp:muc:member#status"), this.rdf.GetLiteral(status));
	return;
   }
	
   if (ctnRoom.IndexOf(member)<0) 
   {
	   ctnRoom.AppendElement(member);
	   this.Assert(member, this.rdf.GetResource("urn:xmpp:muc:member#nick"), this.rdf.GetLiteral(nickname));
	   this.Assert(member, this.rdf.GetResource("urn:xmpp:muc:member#role"), this.rdf.GetLiteral(role));
       this.Assert(member, this.rdf.GetResource("urn:xmpp:muc:member#affiliation"), this.rdf.GetLiteral(affiliation));
       this.Assert(member, this.rdf.GetResource("urn:xmpp:muc:member#status"), this.rdf.GetLiteral(status));
       debug ("member: " + member.Value);
       debug ("nick: " + nickname);
   }
}

nsXmppService.prototype.onAvatar = function (q)
{
	var data = q.firstChild;
	var mime = date.getAttribute("mimetype");
	var b64str = date.firstChild.nodeValue;

}

nsXmppService.prototype.getAvatar = function(to)
{
	var sha1 = Cc["@mozilla.org/cryptoservice;1"].getService().QueryInterface(Ci.nsICryptoService);
	var query = this.createIQ("avatar1", "get", "jabber:iq:avatar", "query");
    var iq = query.parentNode;
	iq.setAttribute("to", to);
	this.schedule["avatar1"]="onAvatar";
	this.send(query.ownerDocument);
}

nsXmppService.prototype.updateRosterPresence = function (from, show, status, sha1)
{
    var ds = this.getDataSource();
	var rdf = this.rdf;
//    var ds = rdf.GetDataSource("file:///tmp/roster.rdf");
    from.match(/(.*)@([^\/]*)/);
    var name = RegExp.$1;
    var domain = RegExp.$2;
    var roster = rdf.GetResource("urn:xmpp:roster");
    var jid = rdf.GetResource("urn:xmpp:roster:jid:"+from);
    var server = rdf.GetResource("urn:xmpp:roster:server:"+domain);
    ctr = this.cu.MakeBag(ds, roster);
    cts = this.cu.MakeSeq(ds, server);

    if (ctr.IndexOf(server)<0)
    {
		ctr.AppendElement(server);
    	this.Assert(server, rdf.GetResource("urn:xmpp:roster#domain"), rdf.GetLiteral(domain));
    }

    if (cts.IndexOf(jid)<0)
    {
		cts.AppendElement(jid);
		this.Assert(jid, rdf.GetResource("urn:xmpp:roster#name"), rdf.GetLiteral(name));
		this.Assert(jid, rdf.GetResource("urn:xmpp:roster#jid"), rdf.GetLiteral(from));
		if (sha1)
			this.Assert(jid, rdf.GetResource("urn:xmpp:roster#avatar"), rdf.GetLiteral(sha1));
    }
    this.Assert(jid, rdf.GetResource("urn:xmpp:presence#show"), rdf.GetLiteral(show));
    this.Assert(jid, rdf.GetResource("urn:xmpp:presence#status"), rdf.GetLiteral(status));
//    out = ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
//    out.FlushTo("file:///tmp/roster.rdf");
}

nsXmppService.prototype.onPresence = function(presence) {
    var from = presence.getAttribute("from");
    var type = presence.getAttribute("type");
    var nodes=presence.childNodes;
    var show = "available";
    var status = "";
    var muc = false;
	var role = "";
	var affiliation = "";
    switch (type)
    {
		case "unavailable":
			show = "";
		break;
		case "subscribe":
			status = "waiting for accept";
			show = "waiting";
		break;
		case "subscribed":
			break;
		case "unsubscribed":
			break;
    }
    for (var i=0; i<nodes.length; i++)
    {	
		var node = nodes.item(i);
		switch (node.nodeName)
		{
			case "status":
			if (node.childNodes.length)
				status = node.firstChild.nodeValue;
			break;
			case "show":
			if (node.childNodes.length)
				show = node.firstChild.nodeValue;
			break;
			case "x":
			if (node.getAttribute("xmlns")=="jabber:iq:avatar")
			{
				var sha1 = node.getElementsByTagName("hash").item(0).firstChild.nodeValue;
			}
			if(node.getAttribute("xmlns")=="http://jabber.org/protocol/muc#user")
			{
				var item = node.getElementsByTagName("item").item(0);
				affiliation = item.getAttribute("affiliation");
				role = item.getAttribute("role");
				muc = true;
			}
			break;
		}
    }
    if (muc)
	    this.updateMucPresence(from, role, affiliation, status, type);
    else
    	this.updateRosterPresence(from, show, status);
}

nsXmppService.prototype.createThread = function (threadName)
{
    var mds	= Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].createInstance();
    return mds.QueryInterface(Ci.nsIRDFDataSource);
}

nsXmppService.prototype.getThreadByName = function (threadName)
{
    /*
       if(!this.threads[threadName])
       {
       this.threads[threadName] = this.createThread(threadName)
       }
       return this.threads[threadName];
     */
    var rdf = Cc[kRDF].getService(Ci.nsIRDFService);
    return rdf.GetDataSource("file:///C|/messages.rdf");
}

nsXmppService.prototype.openMessageWindow = function(jid, thread, subject, type)
{
    var browser = Cc[kWINDOW].getService(nsIWindowWatcher);
    var win = browser.getWindowByName(thread, null);
    var url = "chrome://jazzila/content/chat.xul";
    if (type == "groupchat")
	url = "chrome://jazzila/content/groupchat.xul";
    if (!win)
    {		
		var attrs = { thread: "" , jid: ""};
		win = browser.openWindow(null, url , thread, "chrome,resizable=1", attrs);
		//		win = browser.activeWindow.open("chrome://jazzila/content/thread.xul", thread, "chrome,resizable=1,visible=1");
		win.document.title = subject;
		debug("open: " + thread);
    }
    win.focus();
}

nsXmppService.prototype.appendMessage=function (thread, from, body, me)
{
    var jid = from;
    var urn = this.rdf.GetResource("urn:xmpp:chat:"+thread);
    var ds = this.getDataSource();//this.getThreadByName(thread);
    //	ds.beginUpdateBatch();
    var ctn= this.cu.MakeSeq(ds, urn);
    var id = ctn.GetCount() + 1;
	debug(thread + " " + from + " " + body);
    var msg = this.rdf.GetResource("urn:xmpp:message:" + thread + "/"+ id);
    if (jid.indexOf("@")>-1)
	jid = jid.substring(0, jid.indexOf("@"))
    ctn.AppendElement(msg);
    ds.Assert(msg, this.rdf.GetResource("http://xmpp.org/RDF#timestamp"), this.rdf.GetLiteral(new Date().toString()), true);
    ds.Assert(msg, this.rdf.GetResource("http://xmpp.org/RDF#jid"), this.rdf.GetLiteral(jid), true);
    ds.Assert(msg, this.rdf.GetResource("http://xmpp.org/RDF#style"), this.rdf.GetLiteral(me? "me" : "he"), true);
    ds.Assert(msg, this.rdf.GetResource("http://xmpp.org/RDF#msg"), this.rdf.GetLiteral(body), true);
    //	ds.Assert(msg, rdf.GetResource("http://xmpp.org/RDF#thread"), rdf.GetLiteral(thread), true);
    //	ds.endUpdateBatch();
    //	var rds = ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
    //	rds.Flush();
}

nsXmppService.prototype.onMessage = function (msg)
{
    var body = "";
    var from = msg.getAttribute("from");
    var to = msg.getAttribute("to");
    var type = msg.getAttribute("type");
    var subject = from.substring(0, from.indexOf("/"));
    var nodes=msg.childNodes;
    var thread = from;
    var my = (this.jid == from);
	var ds = this.getDataSource();
    if (type == "groupchat")
	{
		thread = from.substring(0, from.indexOf("/"));
		subject = from.substring(0, from.indexOf("@"));
		from = from.substring(from.indexOf("/")+1);
		var room = this.rdf.GetResource("urn:xmpp:muc:room:"+thread);
		var mynick = this.GetLiteral(room, "urn:xmpp:muc:room#nick");
		my = (mynick == from);
    }

    for (var i=0; i<nodes.length; i++)
    {
	var node = nodes.item(i);
	switch (node.nodeName)
	{
	    case "body":
				if (!node.childNodes.length) 
					continue;
				body = node.firstChild.nodeValue;
	    break;
	    case "thread":
			if (!node.childNodes.length) 
					continue;
			thread = node.firstChild.nodeValue;
	    break;
	    case "subject":
			if (!node.childNodes.length) 
					continue;
			subject = node.firstChild.nodeValue;
		break;
	    case "html":
			html=node;
		break
	    default:
		debug(node.nodeName+": "+node.nodeValue);
	    break;
	}
    }
    this.openMessageWindow(from, thread, subject, type);
    this.appendMessage(thread, from, body, my);
}

nsXmppService.prototype.onStreamFeatures= function (features)
{
    var nodes = features.childNodes;
    var session = false;
    for (var i=0; i<nodes.length; i++)
    {
	var ftr= nodes.item(i);
	debug("feature; "+ftr.nodeName);
	switch(ftr.nodeName)
	{
	    case "starttls": 
		this.hastls=true;
	    break;
	    case "mechanisms":
		for( var j=0; j<ftr.childNodes.length; j++)
		{
		    var m=ftr.childNodes.item(j).firstChild.nodeValue;
		    this.sasl_mechanisms.push(m);
		}
	    break;
            case "bind":
		this.bind();
	    break;
	    case "session":
                session = true;
            break;
	}
    }
    if ( this.state <_AUTHENTICATED) 
	    this.negotiate();
    if ( this.state == _AUTHENTICATED && session) 
		this.session();
}

nsXmppService.prototype.startsasl = function ()
{
    var sha1 = Cc["@mozilla.org/cryptoservice;1"].getService().QueryInterface(Ci.nsICryptoService);
    var doc = dom.implementation.createDocument("","", null);
    var auth = doc.createElementNS( "urn:ietf:params:xml:ns:xmpp-sasl", "auth");
    auth.setAttribute("mechanism", "PLAIN");
    var  str = "\0" + this.user+"\0"+this.pass;
    var bin = Array(0); 
    for (var i=1; i<=this.user.length; i++)
    {
	    bin[i] = this.user.charCodeAt(i-1);
    }
    bin[i] = 0;
    for (var j = 0; j<this.pass.length; j++)
    {
	    bin[++i] = this.pass.charCodeAt(j);
    }
    auth.appendChild(doc.createTextNode(sha1.bin2b64(bin, bin.length)));
    doc.appendChild(auth);
    this.connection.send(doc);
}

nsXmppService.prototype.negotiate = function()
{
    var tls = this.prefBranch.getBoolPref("jazzila.server.tls");
    if (this.register)
    {
	 this.reg1();
    }
    else if (tls)
    {
	this.startsasl();
    }
    else if (this.sasl_mechanisms.length)
    {
	this.startsasl();
    }
    else 
    {
	this.auth1();
    }
}

nsXmppService.prototype.onTextData = function()
{
    if (this.connection.responseText.match(/<stream:stream /))
    {
	this.connection.responseText.match(/(id=[\'\"])([^\'\"]*)/);
	this.streamID=RegExp.$2;
	if(this.connection.responseText.match(/(<stream:features>.*<\/stream:features>)/))
        { 
	    debug(RegExp.$2);
	    var doc = parser.parseFromString("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" +
									RegExp.$1+"</stream:stream>", "text/xml");
    	    //this.onStreamFeatures(features);
	    this.connection.responseXml = doc;
	    this.onXmlData();
	} 
	else 
	if (this.state < _AUTHENTICATED)
	     this.wait("stream:features", 1000, "negotiate");
    }
    if (this.connection.responseText.search(/^<\/stream:stream>/)>-1)
    {
	   this.disconnect();
	   debug("disconnected");
   }
}

nsXmppService.prototype.onStream = function(doc)
{
    var nodes = doc.childNodes;
    for (var i = 0; i<nodes.length; i++)
    {
	var item = nodes.item(i);
	debug("Proceeding: <" + item.nodeName +">");
	switch (item.nodeName)
	{
		case "iq":
		this.onIQ(item);
		break;
		case "presence":
		   this.onPresence(item);
		break;
		case "message":
		   this.onMessage(item);
		break;
		case "stream:features":
		    //this.onStreamFeatures(item);
		    this.fire(item.nodeName, item);
		break;
		case "stream:error":
		   this.onStreamError(item);
		break;
		case "success":
		    this.setState(_AUTHENTICATED);
		    this.initStream();
		break;
		case "failure":
		break;
		case "proceed":
			this.connection.reconnect();
			this.initStream();
			this.startSasl();
		break;
	}
    }
}

nsXmppService.prototype.onXmlData = function()
{
      var doc = this.connection.responseXml.firstChild;
      switch (doc.nodeName)
	{
		case "parsererror":
			this.onTextData();
		break;
		case "body":
			this.onStream(doc);
		break;
		case "stream:stream":
			this.onStream(doc);
		break;
	}
}


nsXmppService.prototype.onData = function()
{
    if(!this.connection.responseXml)
    {
	this.onTextData();
    }
    else 
    {
	this.onXmlData();
    }
}

nsXmppService.prototype.handleEvent = function(event)
{
    this.onData();
	if (this.connection.pending != "" )
	{
		this.send();
	}
}

nsXmppService.prototype.onerror = function ()
{
    if ( this.state == _CONNECTED && this.getPrefBranch.getBoolPref("jazzila.autoreconnect.enabled"))
    {
		this.disconnect();
		this.connect();
    }
}

nsXmppService.prototype.send = function (doc)
{
    var request = "";
    if (doc)
    {
		this.connection.send(doc);
    }
}

nsXmppService.prototype.initDataSources = function()
{
    this.rdf = Cc[kRDF].getService(Ci.nsIRDFService);
    this.cu = Cc["@mozilla.org/rdf/container-utils;1"].getService(Ci.nsIRDFContainerUtils); 
    this.data = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].createInstance();
    this.data = this.data.QueryInterface(Ci.nsIRDFDataSource);
}

nsXmppService.prototype.initStream = function () 
{
    var str = '<stream:stream to="'+ this.domain +
	    '" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">';
    this.connection.write('<?xml version="1.0" encoding="UTF-8"?>');
    this.connection.send(str);
}

nsXmppService.prototype.starttls = function () 
{
	var str = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
	this.connection.send(str);
}

nsXmppService.prototype.initHttpGW = function(gw)
{
	var http_bind = this.prefBranch.getBoolPref("jazzila.http.binding");
	this.connection = http_bind? Cc[kHTTP_BIND].getService(nsIHttpBinding) : Cc[kHTTP_POLL].getService(nsIHttpPolling);
	this.connection.setPollingInterval(this.prefBranch.getIntPref("jazzila.http.polling.interval"));
	this.connection.setUrl(http_gw);
	this.connection.setDomain(this.domain);
}

nsXmppService.prototype.init = function (server, port)
{
    var ssl 	= this.prefBranch.getBoolPref("jazzila.server.ssl");
    
    if (this.http_gw)
    {
		this.initHttpGW(this.http_gw);
    }
    else
    {
		this.connection = new Socket();
		var type = ssl ? ["ssl"]: type;
		this.connection.connect(server, port, type);
		this.initStream();
    }
    this.connection.addEventListener("close", this, false);
    this.connection.addEventListener("error", this, false);
    this.connection.addEventListener("load", this, false);
    this.schedule["stream"] = "onStream";
    this.schedule["stream:features"] = "onStreamFeatures";
    this.schedule["negotiate"] = "negotiate";
}

nsXmppService.prototype.connect = function (host, port, resource)
{
    var user = this.user;
    this.resource=resource;
    this.port=port;
    if (user.match(/(.*)@(.*)/))
    {
	this.user = RegExp.$1;
	this.domain = RegExp.$2;
    }
    else
    {
		this.domain = host;
    }
    this.jid = this.user+"@"+this.domain+"/"+this.resource;
    try 
    {
	this.init(host, port);
    } 
    catch(e)
    {
	dump ("Error: "+ e +"\n");
	this.setState(_DISCONNECTED);
	throw e;
    }
}

nsXmppService.prototype.flush =  function()
{
}

nsXmppService.prototype.disconnect = function ()
{
    if(this.connection)
    {
       try {
	    this.setState(_DISCONNECTED);
	    this.connection.disconnect();
	    this.connection.removeEventListener("error", this, false);
	    this.connection.removeEventListener("load", this, false);
	    this.connection = null;
       }catch(e){
	  debug("Disconnect: " + e);
       }
    }
}

nsXmppService.prototype.groupchat = function(to, message)
{
    var doc = dom.implementation.createDocument("","", null);
    var msg = doc.createElement("message");
    msg.setAttribute("from", this.jid);
    msg.setAttribute("to", to);
    msg.setAttribute("type", "groupchat");
    msg.appendChild(doc.createElement("body")).appendChild(doc.createTextNode(message));
    doc.appendChild(msg);
    this.send(doc);
//    this.appendMessage(to, to+"/"+nick, message, true);
    debug("Sent: " + message + " To: " + to);
}	

nsXmppService.prototype.chat = function(to, subject, message, thread)
{
    var sha1 = Cc["@mozilla.org/cryptoservice;1"].getService(Ci.nsICryptoService);
    var doc = dom.implementation.createDocument("","", null);
    var msg = doc.createElement("message");
    var x = doc.createElementNS("jabber:x:event", "x");
    x.appendChild(doc.createElement("offline"))
    x.appendChild(doc.createElement("delivered"))
    x.appendChild(doc.createElement("displayed"))
    x.appendChild(doc.createElement("composing"))
    //msg.setAttribute("id", sha1.hex_sha1(message+Math.random()));
    msg.setAttribute("to", to);
    //msg.setAttribute("from", this.user + "@" + this.domain);
    msg.setAttribute("from", this.jid);
    msg.setAttribute("type", "chat");
    msg.appendChild(doc.createElement("body")).appendChild(doc.createTextNode(message));
    //	  msg.appendChild(x);
    doc.appendChild(msg);
    this.send(doc);
    this.appendMessage(to, this.user, message, true);
    debug("Sent: " + message + " To: " + to);
}	

nsXmppService.prototype.setLineStatus = function(status, message)
{
    this.setPresence(status, message);
}

nsXmppService.prototype.ping = function ()
{
 debug("-- ping -- pong \n");
 return "pong";
}

	  
/*
 *
 *  Preference code
 *
 */
 
nsXmppService.prototype.getPrefBranch = function(){
  if (!this.prefBranch){ 
    this.prefBranch = Cc['@mozilla.org/preferences-service;1'].getService().QueryInterface(Ci.nsIPrefBranch);
  }
  return this.prefBranch;
}

nsXmppService.prototype.loadLogin = function(url, user, pass){
	var uri = {value: ""};
	debug("Url: " + url);
	try
    {
		var passwordManager= Cc[kPWDMGR_CID].createInstance().QueryInterface(Ci.nsIPasswordManagerInternal);
		passwordManager.findPasswordEntry(url, null, null, uri, user, pass);
    }
    catch(e)
    {
		user.value = null;
		pass.value = null;
    }
}

nsXmppService.prototype.savePasswd = function(url, user, pass){
    var olduser =  {value:""};
    var oldpass = {value:""}; 
    var uri = {value: url};
    try
    {
		var passwordManager= Cc[kPWDMGR_CID].createInstance().QueryInterface(nsIPasswordManager);
		passwordManager.findPasswordEntry(url, null, null, uri, user, pass);
		if (olduser.value && uri.value == url)
			passwordManager.removeUser(url, olduser.value);		
		passwordManager = passwordManager.QueryInterface(Ci.nsIPasswordManager);
		passwordManager.addUser(url, user, pass);
    }
    catch(e)
    {
		debug("Error: " + e);
    }
}

nsXmppService.prototype.prefChanged = function (aPrefName){
  switch(aPrefName){
    case "jazzila.http.polling.interval":
		if (this.connection)
			this.connection.setPollingInterval(this.prefBranch.getIntPref(aPrefName) * 1000);
    break;
  }
}

nsXmppService.prototype.addObserver = function(aDomain, aFunction){
  var myPrefs = this.getPrefBranch();
    var prefBranchInternal = myPrefs.QueryInterface(Ci.nsIPrefBranchInternal);
      if (prefBranchInternal)
          prefBranchInternal.addObserver(aDomain, aFunction, false);
}

nsXmppService.prototype.QueryInterface = function(iid) {
  if (!iid.equals(nsIXmppService) &&
      !iid.equals(Ci.nsIAlertListener) &&
	  !iid.equals(Ci.nsIDOMEventListener) &&
      !iid.equals(nsISupports))
    throw Components.results.NS_ERROR_NO_INTERFACE;
  return this;
}

var jXmppService = new nsXmppService();

/**
 * JS XPCOM component registration goop:
 *
 * We set ourselves up to observe the xpcom-startup category.  This provides
 * us with a starting point.
 */

var nsXmppServiceModule = new Object();

nsXmppServiceModule.registerSelf = function (compMgr, fileSpec, location, type) {
  dump("\n Registering nsXmppService ...");
  compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  compMgr.registerFactoryLocation(kXMPP_UID,
                                  "nsXmppService",
                                  kXMPP_CID,
                                  fileSpec, 
                                  location, 
                                  type);
  debug(" ok ");
}

nsXmppServiceModule.getClassObject = function (compMgr, cid, iid) {
  if (!cid.equals(kXMPP_UID))
    throw Components.results.NS_ERROR_NO_INTERFACE;

  if (!iid.equals(Ci.nsIFactory))
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

  return nsXmppServiceFactory;
}

nsXmppServiceModule.canUnload = function (compMgr) {
  // cleanup
  if (jXmppService.state > _DISCONNECTED)
  {
	  jXmppService.disConnect();
  }
  jXmppService.listeners = null;

  return true;
}

var nsXmppServiceFactory = new Object();

nsXmppServiceFactory.createInstance = function (outer, iid) {
  if (outer != null)
    throw Components.results.NS_ERROR_NO_AGGREGATION;

  if (!iid.equals(nsIXmppService) &&
      !iid.equals(Ci.nsIAlertListener) &&
	  !iid.equals(Ci.nsIDOMEventListener) &&
      !iid.equals(nsISupports))
    throw Components.results.NS_ERROR_NO_INTERFACE;
  return jXmppService;
}

function NSGetModule(compMgr, fileSpec) {
  return nsXmppServiceModule;
}

/*
  Helper Functions
*/

/*
** Scheduler 
*/
nsXmppService.prototype.wait = function (event, timeout, alt)
{
    var me = this;
    var obj  = {
        timer:  Cc[kTIMER_CID].getService(Ci.nsITimer),
        func: alt, 
	ctx : me,
	notify: function (){
	   if(this.func in this.ctx)
			this.ctx[this.func]();
       }
    };
    this.queue[event] = obj;
    obj.timer.initWithCallback(obj, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
}

nsXmppService.prototype.addSchedule = function (event, cbfunct)
{
    this.schedule[event] = cbfunct;
}

nsXmppService.prototype.fire = function (event, ctx)
{
    var func = this.schedule[event];
    var alt = this.queue[event];
    if (alt)
    {
	alt.timer.cancel();
    }
    if (func in this) 
    {
         this[func](ctx);
    }
}

/*
**  Socket
*/
function Socket ()
{
    this.listeners=new Array();
    var parent = this;
    this.sockService = Cc["@mozilla.org/network/socket-transport-service;1"].getService(Ci.nsISocketTransportService);
    if (!this.sockService)
	    throw ("Couldn't get socket service.");
    this.dataListener = {
		onStartRequest: function(request, context){ },
		onStopRequest: function(request, context, status){ },
		onDataAvailable: function(request, context, istream, offset, count){
			var data = parent.input.readBytes(count);
			parent.responseText += data;
			data=data.replace(/>[ \t]*$/,">");
			if(!data.match(/<\/?.*\/?>$/))
				return;
			debug("Response("+count+"): '"+parent.responseText+"'");
			try
			{
				parent.responseXml = parser.parseFromString("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
									+parent.responseText+"</stream:stream>", "text/xml");
			} catch (e){
				parent.responseXml = null;
				debug(e);
			}
			parent.handleEvent();
			parent.responseText="";
		},
    };
}
Socket.prototype.host = "";
Socket.prototype.port = 5222;
Socket.prototype.type = null;
Socket.prototype.pending = "";
Socket.prototype.responseXML = null;
Socket.prototype.responseText = "";
Socket.prototype.input = null;
Socket.prototype.reconnect = function()
{
    var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
    this.transport = this.sockService.createTransport(this.type, (this.type ? this.type.length: 0), this.host, this.port, null);
    var istream = this.transport.openInputStream(0, 0, 0);
    if (!istream)
	    throw "Error getting input stream.";
    this.input = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
    this.input.setInputStream(istream);
    var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(Ci.nsIInputStreamPump);
    pump.init(istream, -1, -1, 0, 0, false);
    pump.asyncRead(this.dataListener,this);
    /* no limit on the output stream buffer */
    this.ostream = this.transport.openOutputStream(0,  0, 0);
    if (!this.ostream)
	    throw "Error getting output stream.";
    this.output = Components.classes["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
    this.output.setOutputStream(this.ostream);
}

Socket.prototype.connect = function (host, port, type)
{
    this.host = host;
    this.port = port;
    this.type = type;
    this.reconnect();
}

Socket.prototype.disconnect = function ()
{
    this.output.close();
    this.input.close();
}

Socket.prototype.write = function (req)
{
	var str = "";
	if (!req)
		return;
	if(req instanceof XMLDocument)
		str = xmlserialiser.serializeToString(req);
	else 
		str = req.toString();
	this.pending += str;
}

Socket.prototype.send = function (req)
{
    if (!req && !this.pending)
			return;
	this.write(req);
	try 
	{
		if (this.ostream)
		{
			this.ostream.write(this.pending, this.pending.length);
			debug("Sent: " + this.pending);
			this.ostream.flush();
			this.pending = "";
		}
   }
   catch(e) 
   {
		debug (e);
		this.disconnect();
		throw e;
   }
}

Socket.prototype.getListenersByType= function(type){
  if (!this.listeners[type]) {
	this.listeners[type] = new Array();
  }
  return this.listeners[type];
}

Socket.prototype.addEventListener = function(type, aListener, capture)
{
  var lsr = this.getListenersByType(type);
  var run = 0;
  var found = false;
  while (!found && (run < lsr.length)){
    if (lsr[run] == aListener) {
      found = true;
	  break;
    }
    run++;  
  }
  if(!found)
	lsr.push(aListener);
}

Socket.prototype.removeEventListener = function(type, aListener, capture) {
  var run = 0;
  var lsr = this.getListenersByType(type);
  
  if(lsr== null)
    return;
    
  while (run < lsr.length) {
    if (lsr[run] == aListener) {
	  lsr.removeElementAt(run);
	  break;
    }
    run++;  
  }
}

Socket.prototype.dispatchEvent = function (lsr)
{
    if (!lsr)
		return;
	for(var i=0;i < lsr.length; i ++){
		lsr[i].handleEvent(null);
	}
}

Socket.prototype.handleEvent = function()
{
	this.dispatchEvent(this.getListenersByType("load"));
}

function debug(aMsg){
if(_DEBUG)
    dump(aMsg  + "\n");
}
